unit InventionCalc;

interface

uses
  GlobalRecordsAndEnums, XMLDoc, Classes, IniFiles, Decryptor, Category, Items, Shopping, Results, Location,
  HashTable, UserSettings, Region;

type
  TInventionCalc = Class
    private
      _amarrDecryptorCount: Integer;
      _caldariDecryptorCount: Integer;
      _gallenteDecryptorCount: Integer;
      _minmatarDecryptorCount: Integer;

      DecryptorTypes: TList;
      Decryptors: TList;
      Categories: TList;
      Subcategories: TList;
      Groups: TList;

      NewItems: THashTable;
      NewT1Items: array of TNewItem; // quick access array to all TNewItem instances with type = IT_T1Item
      procedure LoadNewItems;
      function LoadNewItems_v041(stream: TMemoryStream): Boolean;
      function LoadUserItems_v041(stream: TMemoryStream): Boolean;
      procedure LoadResults;
      function LoadResults_v041(stream: TMemoryStream): Boolean;
      procedure SaveNewItems;

      function GetDecryptorTypeCount: Integer;

      function GetCategoryCount: Integer;
      function GetSubcategoryCount: Integer;
      function GetGroupCount: Integer;

      procedure LinkShoppingListItems;
    public
      UserSettings: TUserSettings;
      ShoppingList: TShoppingList;
      Results: TResults;
      Locations: TLocations;
      Regions: TRegions;

      constructor Create;
      destructor Destroy; override;

      procedure SaveNewItemsText;
      function LoadNewItemsText:Boolean;
      procedure LoadDecryptors;
      procedure LoadCategories;
      procedure LoadSubCategories;
      procedure LoadGroups;

      procedure SaveCategories;

      function GetDecryptorTypeByID(typeID: Integer): TDecryptorType;
      function GetDecryptorType(index: Integer): TDecryptorType;
      function GetDecryptor(Race: ERace; index: Integer): TDecryptor;
      function GetDecryptorByID(id: Integer): TDecryptor;

      function AddCategory(name: String): TCategory;
      function GetCategory(index: Integer): TCategory;
      function GetCategoryByID(id: Integer): TCategory;
      function RemoveCategory(category: TCategory): Boolean;

      function AddSubcategory(name: String; category: TCategory): TSubcategory;
      function GetSubcategory(index: Integer): TSubcategory;
      function GetSubcategoryByID(id: Integer): TSubcategory;
      function RemoveSubcategory(subcategory: TSubCategory): Boolean;

      function AddGroup(name: String; subcategory: TSubcategory): TGroup;
      function GetGroup(index: Integer): TGroup;
      function GetGroupByID(id: Integer): TGroup;
      function RemoveGroup(group: TGroup): Boolean;

//========================================
      function AddItem(item: TNewItem): Boolean;
      procedure Delete(ID: Integer);
      function GetItem(ID: Integer): TNewItem;
      function GetItemByIndex(index: Integer): TNewItem;
      function GetT1ItemByIndex(index: Integer): TNewItem;
      function GetNewItemCount: Integer;
      function GetT1ItemCount: Integer;
      procedure ClearItems;
      procedure LinkItems(loaduseritems: Boolean);
//========================================
      property DecryptorTypeCount: Integer read GetDecryptorTypeCount;
      property AmarrDecryptorCount: Integer read _amarrDecryptorCount;
      property CaldariDecryptorCount: Integer read _caldariDecryptorCount;
      property GallenteDecryptorCount: Integer read _gallenteDecryptorCount;
      property MinmatarDecryptorCount: Integer read _minmatarDecryptorCount;

      property CategoryCount: Integer read GetCategoryCount;
      property SubcategoryCount: Integer read GetSubcategoryCount;
      property GroupCount: Integer read GetGroupCount;
  end;

implementation

uses
  Forms, Dialogs, SysUtils;

constructor TInventionCalc.Create;
begin
  Inherited Create;
  UserSettings := TUserSettings.Create;
  ShoppingList := TShoppingList.Create;
  Locations := TLocations.Create;
  Regions := TRegions.Create;

  { load Decryptors }
  DecryptorTypes := TList.Create;
  Decryptors := TList.Create;
  LoadDecryptors;

  { load Categories }
  Categories := TList.Create;
  LoadCategories;

  { load Subcategories }
  SubCategories := TList.Create;
  LoadSubcategories;

  { load Groups }
  Groups := TList.Create;
  LoadGroups;

  NewItems := THashTable.Create(2000, true);
  SetLength(NewT1Items, 0);
  LoadNewItems;

  LinkShoppingListItems;
  Results := TResults.Create;
  LoadResults;
end;

destructor TInventionCalc.Destroy;
var
  i: Integer;
begin
  Results.Save;
  UserSettings.Destroy;
  ShoppingList.Destroy;
  Results.Destroy;
  Locations.Destroy;
  Regions.Destroy;

  SaveNewItems;
  NewItems.Destroy;

  for i := 0 to DecryptorTypes.Count -1 do begin
    TDecryptorType(DecryptorTypes.Items[i]).Destroy;
  end;

  for i := 0 to Decryptors.Count -1 do begin
    TDecryptor(Decryptors.Items[i]).Destroy;
  end;
  for i := 0 to Categories.Count -1 do begin
    TCategory(Categories.Items[i]).Destroy;
  end;
  for i := 0 to Subcategories.Count -1 do begin
    TSubcategory(Subcategories.Items[i]).Destroy;
  end;
  for i := 0 to Groups.Count -1 do begin
    TGroup(Groups.Items[i]).Destroy;
  end;

  Inherited Destroy;
end;

//=========================================
//============ Load procedures ============
//=========================================

procedure TInventionCalc.LoadDecryptors;
var
  stream: TMemoryStream;
  decr: SDecryptor;
  decryptor: TDecryptor;
  decryptortype: SDecryptorType;
  i, decryptortypecount: Integer;
begin
  _amarrDecryptorCount := 0;
  _caldariDecryptorCount := 0;
  _gallenteDecryptorCount := 0;
  _minmatarDecryptorCount := 0;

  if (not FileExists('res\decryptors')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\res\decryptors.', mtError, [mbOk], 0);
  end
  else begin
    Decryptors.Clear;

    stream := TMemoryStream.Create;
    stream.Position := 0;
    stream.LoadFromFile('res\decryptors');
    try
      stream.Read(decryptortypecount, SizeOf(Integer));
    except
    on e:exception do
       begin
          MessageDlg('Error reading decryptor type count: '+e.Message, mtError, [mbOk], 0);
          exit;
       end;
    end;
    for i := 0 to decryptortypecount -1 do begin
      stream.Read(decryptortype, SizeOf(SDecryptorType));
      DecryptorTypes.Add(TDecryptorType.Create(decryptortype));
    end;
    while stream.Position < stream.Size do
    begin
      try
        stream.Read(decr, sizeof(SDecryptor));
      except
      on e:exception do
         begin
           MessageDlg('Error loading Decryptor: '+e.Message, mtError, [mbOk], 0);
           exit;
         end;
      end;
      if (decr.Race = Amarr) then Inc(_amarrDecryptorCount)
      else if (decr.Race = Caldari) then Inc(_caldariDecryptorCount)
      else if (decr.Race = Gallente) then Inc(_gallenteDecryptorCount)
      else if (decr.Race = Minmatar) then Inc(_minmatarDecryptorCount);
      decryptor := TDecryptor.Create(decr, GetDecryptorTypeByID(decr.TypeID));
      Decryptors.Add(decryptor);
    end;
    stream.Free;
  end;
end;

procedure TInventionCalc.LoadCategories;
var
  stream: TMemoryStream;
  cat: SCategory;
  category: TCategory;
begin
  if (not FileExists('res\Categories.dat')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\res\Categories.dat.', mtError, [mbOk], 0);
  end
  else begin
    Categories.Clear;

    stream := TMemoryStream.Create;
    stream.Position := 0;
    stream.LoadFromFile('res\Categories.dat');
    while stream.Position < stream.Size do
    begin
      try
        stream.Read(cat, sizeof(SCategory));
      except
      on e:exception do
         begin
           MessageDlg('Error loading Category:'+e.Message, mtError, [mbOk], 0);
          exit;
         end;
      end;
      category := TCategory.Create(cat);
      Categories.Add(category);
    end;
    stream.Free;
  end;
end;

procedure TInventionCalc.LoadSubCategories;
var
  stream: TMemoryStream;
  category: TCategory;
  subcat: SSubCategory;
  subcategory: TSubCategory;
begin
  if (not FileExists('res\Subcategories.dat')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\res\Subcategories.dat.', mtError, [mbOk], 0);
  end
  else begin
    SubCategories.Clear;

    stream := TMemoryStream.Create;
    stream.Position := 0;
    stream.LoadFromFile('res\Subcategories.dat');
    while stream.Position < stream.Size do
    begin
      try
        stream.Read(subcat, sizeof(SSubcategory));
      except
      on e:exception do
        begin
          MessageDlg('Error loading Subcategory: '+e.Message, mtError, [mbOk], 0);
          exit;
        end;
      end;
      category := self.GetCategoryByID(subcat.Category);
      subcategory := TSubCategory.Create(subcat, category);
      Subcategories.Add(subcategory);
    end;
    stream.Free;
  end;
end;

procedure TInventionCalc.LoadGroups;
var
  stream: TMemoryStream;
  subcategory: TSubCategory;
  grp: SGroup;
  group: TGroup;
begin
  if (not FileExists('res\Groups.dat')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\res\Groups.dat.', mtError, [mbOk], 0);
  end
  else begin
    Groups.Clear;

    stream := TMemoryStream.Create;
    stream.Position := 0;
    stream.LoadFromFile('res\Groups.dat');
    while stream.Position < stream.Size do
    begin
      try
        stream.Read(grp, sizeof(SGroup));
      except
      on e:exception do
         begin
           MessageDlg('Error loading Group: '+e.Message, mtError, [mbOk], 0);
           exit;
         end;
      end;
      subcategory := self.GetSubcategoryByID(grp.SubCategory);
      group := TGroup.Create(grp, subcategory);
      Groups.Add(group);
    end;
    stream.Free;
  end;
end;

// =========================================
// ============ Save procedures ============
// =========================================

procedure TInventionCalc.SaveCategories;
var
  category: TCategory;
  subcategory: TSubcategory;
  group: TGroup;
  cat: SCategory;
  subcat: SSubcategory;
  grp: SGroup;
  i: Integer;
  stream: TMemoryStream;
begin
  stream := TMemoryStream.Create;
  for i := 0 to Categories.Count -1 do begin
    category := TCategory(Categories.Items[i]);
    cat.ID := category.ID;
    cat.Name := category.Name;
    stream.Write(cat, SizeOf(SCategory));
  end;
  try
    stream.SaveToFile('res\Categories.dat');
  except
    on e: Exception do MessageDlg('Can not save res\Categories.dat: '+e.Message, mtError, [mbOk], 0);
  end;
  stream.Free;
  stream := TMemoryStream.Create;
  for i := 0 to Subcategories.Count -1 do begin
    subcategory := TSubcategory(Subcategories.Items[i]);
    subcat.ID := subcategory.ID;
    subcat.Name := subcategory.Name;
    subcat.Category := subcategory.Category.ID;
    stream.Write(subcat, SizeOf(SSubcategory));
  end;
  try
    stream.SaveToFile('res\Subcategories.dat');
  except
    on e: Exception do MessageDlg('Can not save res\Subcategories.dat:'+e.Message, mtError, [mbOk], 0);
  end;
  stream.Free;
  stream := TMemoryStream.Create;
  for i := 0 to Groups.Count -1 do begin
    group := TGroup(Groups.Items[i]);
    grp.ID := group.ID;
    grp.Name := group.Name;
    grp.SubCategory := group.Subcategory.ID;
    stream.Write(grp, SizeOf(SGroup));
  end;
  try
    stream.SaveToFile('res\Groups.dat');
  except
    on e: Exception do MessageDlg('Can not save res\Groups.dat: '+e.Message, mtError, [mbOk], 0);
  end;
  stream.Free;
end;

//==========================================
//=========== Decryptors wrapper ===========
//==========================================

function TInventionCalc.GetDecryptorTypeCount: Integer;
begin
  result := DecryptorTypes.Count;
end;

function TInventionCalc.GetDecryptorTypeByID(typeID: Integer): TDecryptorType;
var
  i: Integer;
begin
  result := nil;
  for i := 0 to DecryptorTypes.Count -1 do begin
    if (TDecryptorType(DecryptorTypes[i]).ID = typeID) then begin
      result := TDecryptorType(DecryptorTypes[i]);
      break;
    end;
  end;
end;

function TInventionCalc.GetDecryptorType(index: Integer): TDecryptorType;
begin
  if (index < DecryptorTypes.Count) then result := TDecryptorType(DecryptorTypes[index])
  else result := nil;
end;

function TInventionCalc.GetDecryptor(Race: ERace; index: Integer): TDecryptor;
{
  returns the indexed component of the race (f.e. "The second amarr component"). Returns nil when out of bounds.
}
var
  i, count: Integer;
begin
  count := 0;
  result := nil;
  for i := 0 to Decryptors.Count -1 do begin
    if (TDecryptor(Decryptors[i]).Race = Race) then begin
      if (count = index) then begin
        result := TDecryptor(Decryptors[i]);
        break;
      end
      else Inc(count);
    end;
  end;
end;

function TInventionCalc.GetDecryptorByID(id: Integer): TDecryptor;
var
  i: Integer;
begin
  result := nil;
  for i := 0 to Decryptors.Count -1 do begin
    if (TDecryptor(Decryptors.Items[i]).ID = id) then begin
      result := TDecryptor(Decryptors.Items[i]);
      break;
    end;
  end;
end;

//==========================================
//=========== Categories wrapper ===========
//==========================================
function TInventionCalc.AddCategory(name: String): TCategory;
var
  category: TCategory;
  id: Integer;
begin
  if (Categories.Count = 0) then id := 0
  else id := TCategory(Categories.Items[Categories.Count -1]).ID + 1;
  category := TCategory.Create(name, id);
  Categories.Add(category);
  result := category;
end;

function TInventionCalc.GetCategoryCount: Integer;
begin
  result := Categories.Count;
end;

function TInventionCalc.GetCategory(index: Integer): TCategory;
begin
  result := TCategory(Categories.Items[index]);
end;

function TInventionCalc.GetCategoryByID(id: Integer): TCategory;
var
  i: Integer;
begin
  result := nil;
  for i := 0 to Categories.Count -1 do begin
    if (TCategory(Categories.Items[i]).ID = id) then result := TCategory(Categories.Items[i]);
  end;
end;

function TInventionCalc.RemoveCategory(category: TCategory): Boolean;
var
  i: Integer;
  subcategory: TSubcategory;
  item: TNewItem;
begin
  result := Categories.Remove(category) >= 0;
  for i := 0 to category.SubcategoryCount -1 do begin
    subcategory := category.GetSubcategory(i);
    result := result and self.RemoveSubcategory(subcategory);
  end;
  for i := 0 to GetT1ItemCount -1 do begin
    item := GetT1ItemByIndex(i);
    if (item.Category = category) then begin
      item.Category := nil;
      item.Subcategory := nil;
      item.Group := nil;
    end;
  end;
  category.Destroy;
end;

//=============================================
//=========== Subcategories wrapper ===========
//=============================================
function TInventionCalc.AddSubcategory(name: String; category: TCategory): TSubcategory;
var
  subcategory: TSubcategory;
  id: Integer;
begin
  if (Subcategories.Count = 0) then id := 0
  else id := TSubcategory(Subcategories.Items[Subcategories.Count -1]).ID + 1;
  subcategory := TSubcategory.Create(name, id, category);
  Subcategories.Add(subcategory);
  result := subcategory;
end;

function TInventionCalc.GetSubcategoryCount: Integer;
begin
  result := Subcategories.Count;
end;

function TInventionCalc.GetSubcategory(index: Integer): TSubcategory;
begin
  result := TSubcategory(Subcategories.Items[index]);
end;

function TInventionCalc.GetSubcategoryByID(id: Integer): TSubcategory;
var
  i: Integer;
begin
  result := nil;
  for i := 0 to Subcategories.Count -1 do begin
    if (TSubcategory(Subcategories.Items[i]).ID = id) then result := TSubcategory(Subcategories.Items[i]);
  end;
end;

function TInventionCalc.RemoveSubcategory(subcategory: TSubCategory): Boolean;
var
  i: Integer;
  group: TGroup;
  item: TNewItem;
begin
  result := Subcategories.Remove(subcategory) >= 0;
  for i := subcategory.GroupCount -1 downto 0 do begin
    group := subcategory.GetGroup(i);
    result := result and self.RemoveGroup(group);
  end;
  subcategory.category.RemoveSubcategory(subcategory);
  for i := 0 to GetT1ItemCount -1 do begin
    item := GetT1ItemByIndex(i);
    if (item.Subcategory = subcategory) then begin
      item.Category := nil;
      item.Subcategory := nil;
      item.Group := nil;
    end;
  end;
  subcategory.Destroy;
end;

//======================================
//=========== Groups wrapper ===========
//======================================
function TInventionCalc.AddGroup(name: String; subcategory: TSubCategory): TGroup;
var
  group: TGroup;
  id: Integer;
begin
  if (Groups.Count = 0) then id := 0
  else id := TGroup(Groups.Items[Groups.Count -1]).ID + 1;
  group := TGroup.Create(name, id, subcategory);
  Groups.Add(group);
  result := group;
end;

function TInventionCalc.GetGroupCount: Integer;
begin
  result := Groups.Count;
end;

function TInventionCalc.GetGroup(index: Integer): TGroup;
begin
  result := TGroup(Groups.Items[index]);
end;

function TInventionCalc.GetGroupByID(id: Integer): TGroup;
var
  i: Integer;
begin
  result := nil;
  for i := 0 to Groups.Count -1 do begin
    if (TGroup(Groups.Items[i]).ID = id) then result := TGroup(Groups.Items[i]);
  end;
end;

function TInventionCalc.RemoveGroup(group: TGroup): Boolean;
var
  i: Integer;
  item: TNewItem;
begin
  result := Groups.Remove(group) >= 0;
  group.Subcategory.RemoveGroup(group);
  for i := 0 to GetT1ItemCount -1 do begin
    item := GetT1ItemByIndex(i);
    if (item.Group = group) then begin
      item.Category := nil;
      item.Subcategory := nil;
      item.Group := nil;
    end;
  end;
  group.Destroy;
end;

//========================================
//                new stuff
//========================================

function TInventionCalc.AddItem(item: TNewItem): Boolean;
begin
  result := NewItems.Insert(item.ID, item);
end;

procedure TInventionCalc.Delete(ID: Integer);
begin
  NewItems.Delete(ID);
end;

function TInventionCalc.GetItem(ID: Integer): TNewItem;
begin
  result := TNewItem(NewItems.Search(ID));
end;

function TInventionCalc.GetItemByIndex(index: Integer): TNewItem;
// note that this pulls items directly from the hash table, so quite a few values will return nil!
begin
  result := TNewItem(NewItems.GetItem(index));
end;

function TInventionCalc.GetT1ItemByIndex(index: Integer): TNewItem;
begin
  result := NewT1Items[index];
end;

function TInventionCalc.GetNewItemCount: Integer;
begin
  result := NewItems.Size;
end;

function TInventionCalc.GetT1ItemCount: Integer;
begin
  result := Length(NewT1Items);
end;

procedure TInventionCalc.LoadNewItems;
var
  stream: TMemoryStream;
  version: Double;
begin
  if (not FileExists('res\NewItems.dat')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\res\NewItems.dat.', mtError, [mbOk], 0);
  end
  else begin
    stream := TMemoryStream.Create;
    stream.Position := 0;
    stream.LoadFromFile('res\NewItems.dat');
    try
      stream.Read(version, sizeof(Double));
    except
    on e:Exception do
    begin
      MessageDlg('Error reading NewItems.dat version information: '+e.Message, mtError, [mbOk], 0);
      stream.Free;
      exit;
      end;
    end;
     self.LoadNewItemsText;
{    if (Round(version * 100) = 41) then // just 0.41 seems to result in a precision error
      self.LoadNewItems_v041(stream)
    else begin
      MessageDlg('Error reading NewItems.dat: no suitable load function found.', mtError, [mbOk], 0);
      stream.Free;
      exit;
    end;}
    stream.Free;
  end;

  LinkItems(true);
end;

procedure TInventionCalc.LinkItems(loaduseritems: Boolean);
var
  stream: TMemoryStream;
  version: Double;
  item, linkeditem: TNewItem;
  i, j: Integer;
  itemamount: TItemAmount;
begin
  // Link item requirements
  for i := 0 to NewItems.Size -1 do begin
    item := TNewItem(NewItems.GetItem(i));
    if (item <> nil) then begin
      for j := item.UsedInCount -1 downto 0 do begin
        itemamount := item.GetUsedIn(j);
        linkeditem := TNewItem(NewItems.Search(itemamount.ID));
        item.SetLinkedItem(itemamount, linkeditem);
      end;
      for j := item.RequirementsCount -1 downto 0 do begin
        itemamount := item.GetRequirement(j);
        linkeditem := TNewItem(NewItems.Search(itemamount.ID));
        item.SetLinkedItem(itemamount, linkeditem);
      end;
    end;
  end;

  // load user items
  if (loaduseritems) then begin
    if (not FileExists('user\UserItems.dat')) then begin
      MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\user\UserItems.dat.', mtError, [mbOk], 0);
    end
    else begin
      stream := TMemoryStream.Create;
      stream.Position := 0;
      stream.LoadFromFile('user\UserItems.dat');
      try
        stream.Read(version, sizeof(Double));
      except
      on e:Exception do
      begin
        MessageDlg('Error reading UserItems.dat version information: '+e.Message, mtError, [mbOk], 0);
        stream.Free;
        exit;
      end;
      end;
      if (Round(version * 100) = 41) then // just 0.41 seems to result in a precision error
        self.LoadUserItems_v041(stream)
      else begin
        MessageDlg('Error reading UserItems.dat: no suitable load function found.', mtError, [mbOk], 0);
        stream.Free;
        exit;
      end;
      stream.Free;
    end;
  end;
end;

function TInventionCalc.LoadNewItems_v041(stream: TMemoryStream): Boolean;
{
  Loads NewItems.dat for the 0.41 version of the invention calculator
}
var
  itemtoread: SNewItem;
  item: TNewItem;
  i, len: Integer;
  itemidamount: SItemIDAmount;
begin
  result := true;
  while stream.Position < stream.Size do begin
    try
      stream.Read(itemtoread, sizeof(SNewItem));
      item := TNewItem.Create(itemtoread);
      for i := 0 to itemtoread.UsedInAmount -1 do begin
        stream.Read(itemidamount, SizeOf(SItemIDAmount));
        item.AddUsedIn(itemidamount.ItemID, nil, itemidamount.Amount, itemidamount.Damage);
      end;
      for i := 0 to itemtoread.RequirementAmount -1 do begin
        stream.Read(itemidamount, SizeOf(SItemIDAmount));
        item.AddRequirement(itemidamount.ItemID, nil, itemidamount.Activity, itemidamount.Amount, itemidamount.ApplyWaste, itemidamount.Damage);
      end;
      NewItems.Insert(item.ID, item);
      if (item.ItemType = IT_T1Item) then begin
        len := Length(NewT1Items);
        SetLength(NewT1Items, len +1);
        NewT1Items[len] := item;
      end;
    except
    on e:Exception do
    begin
      MessageDlg('Error reading NewItems.dat: '+e.Message, mtError, [mbOk], 0);
      result := false;
      exit;
    end;
    end;
  end;
end;

function TInventionCalc.LoadNewItemsText: Boolean;
{
  Loads NewItems.dat for the 0.41 version of the invention calculator
}
var
  itemtoread: SNewItem;
  item: TNewItem;
  i,n, len: Integer;
  itemidamount: SItemIDAmount;
  strings:TStringList;
  global_strings:TStringList;
  function readSNewItem(S:String):SNewItem;
  begin
    strings.Clear;
    ExtractStrings([#9],[],PChar(s),strings);

    result.Name:=strings[0];
    result.ID:=StrToInt(strings[1]);
    result.ItemType:=StringToEItemType(strings[2]);
    result.Volume:=StrToFloat(strings[3]);
    result.Race:= StringToERace(strings[4]);
    result.BuildTime:=StrToInt(strings[5]);
    result.ProductivityModifier:=StrToInt(strings[6]);
    result.InventionTime:=StrToInt(strings[7]);
    result.InventionType:=StringToEInventionType(strings[8]);
    result.InventionRace:=StringToERace(strings[9]);
    result.CopyTime:=StrToInt(strings[10]);
    result.ProductionLimit:=StrToInt(strings[11]);
    result.BaseChance:=StrToFloat(strings[12]);
    result.WasteFactor:=StrToInt(strings[13]);
    result.BuildType:=StringToEInventionType(strings[14]);
    result.PortionSize:=StrToInt(strings[15]);
    result.UsedInAmount:=StrToInt(strings[16]);
    result.RequirementAmount:=StrToInt(strings[17]);
    result.InventedWithAmount:=StrToInt(strings[18]);
  end;

  function readRequirements(S:String):SItemIDAmount;
  begin
    strings.Clear;
    ExtractStrings([#9],[],PChar(s),strings);

    result.ItemID:=StrToInt(strings[1]);
    result.Activity:= StringToEActivity(strings[2]);
    result.Amount:= StrToInt(strings[3]);
    result.ApplyWaste:= StrToBool(strings[4]);
    result.Damage:= StrToFloat(strings[5]);
  end;

begin
  strings:=TStringList.Create;
  global_strings:=TStringList.Create;
  strings.Delimiter:=#9;
  global_strings.LoadFromFile('res\NewItems.txt');
  result := true;
  n:=0;
  while n < global_strings.Count do begin
    try
      itemtoread:=readSNewItem(global_strings[n]);
      item := TNewItem.Create(itemtoread);
      n:=n+1;
      for i := 0 to itemtoread.UsedInAmount -1 do begin
        itemidamount:=readRequirements(global_strings[n]);
        item.AddUsedIn(itemidamount.ItemID, nil, itemidamount.Amount, itemidamount.Damage);
        n:=n+1;
      end;
      for i := 0 to itemtoread.RequirementAmount -1 do begin
        itemidamount:=readRequirements(global_strings[n]);
        item.AddRequirement(itemidamount.ItemID, nil, itemidamount.Activity, itemidamount.Amount, itemidamount.ApplyWaste, itemidamount.Damage);
        n:=n+1;
      end;
      NewItems.Insert(item.ID, item);
      if (item.ItemType = IT_T1Item) then begin
        len := Length(NewT1Items);
        SetLength(NewT1Items, len +1);
        NewT1Items[len] := item;
      end;
    except
    on e:Exception do
      begin
        MessageDlg('Error reading NewItems.txt: '+e.Message, mtError, [mbOk], 0);
        result := false;
        exit;
      end;
    end;
  end;
  strings.Free;
  global_strings.Free;
end;



function TInventionCalc.LoadUserItems_v041(stream: TMemoryStream): Boolean;
{
  Loads UserItems.dat for the 0.41 version of the invention calculator
}
var
  decr: SDecryptorUserSetting;
  decryptor: TDecryptor;
  i: Integer;
  item: TNewItem;
  useritemtoread: SNewItemUserSetting;
begin
  result := true;
  for i := 0 to Decryptors.Count -1 do begin
    try
      stream.Read(decr, sizeof(SDecryptorUserSetting));
      decryptor := self.GetDecryptorByID(decr.ID);
      if (decryptor <> nil) then decryptor.Price := decr.Price;
    except
    on e:Exception do
    begin
      MessageDlg('Error reading UserItems.dat - invalid decryptor: '+e.Message, mtError, [mbOk], 0);
      result := false;
      exit;
    end;
    end;
  end;
  while stream.Position < stream.Size do begin
    try
      stream.Read(useritemtoread, sizeof(SNewItemUserSetting));
      item := TNewItem(NewItems.Search(useritemtoread.ID));
      if (item <> nil) then begin
        item.LoadUserSetting(useritemtoread);
        item.Category := GetCategoryByID(useritemtoread.CategoryID);
        item.Subcategory := GetSubcategoryByID(useritemtoread.SubcategoryID);
        item.Group := GetGroupByID(useritemtoread.GroupID);
      end;
    except
    on e:Exception do
    begin
      MessageDlg('Error reading UserItems.dat: '+e.Message, mtError, [mbOk], 0);
      result := false;
      exit;
    end;
    end;
  end;
end;


procedure TInventionCalc.SaveNewItems;
{
  Saves NewItems.dat and UserItems.dat, as latest version.
}
var
  i, j: Integer;
  decr: SDecryptorUserSetting;
  itemidamount: SItemIDAmount;
  item: TNewItem;
  itemtowrite: SNewItem;
  itemtowrite2: SNewItemUserSetting;
  stream, stream2: TMemoryStream;
  version: Double;
begin
  version := 0.41;
  stream := TMemoryStream.Create;
  stream.Write(version, SizeOf(Double));
  stream2 := TMemoryStream.Create;
  stream2.Write(version, SizeOf(Double));
  for i := 0 to Decryptors.Count -1 do begin
    decr.ID := TDecryptor(Decryptors[i]).ID;
    decr.Price := TDecryptor(Decryptors[i]).Price;
    stream2.Write(decr, SizeOf(SDecryptorUserSetting));
  end;
  for i := 0 to NewItems.Size -1 do begin
    item := TNewItem(NewItems.GetItem(i));
    if (item <> nil) then begin
      itemtowrite := item.ToSNewItem;
      stream.Write(itemtowrite, SizeOf(SNewItem));
      for j := 0 to item.UsedInCount -1 do begin
        itemidamount.ItemID := item.GetUsedIn(j).Item.ID;
        itemidamount.Activity := ACT_None;
        itemidamount.Amount := item.GetUsedIn(j).Amount;
        itemidamount.ApplyWaste := item.GetUsedIn(j).ApplyWaste;
        itemidamount.Damage := item.GetUsedIn(j).Damage;
        stream.Write(itemidamount, SizeOf(SItemIDAmount));
      end;
      for j := 0 to item.RequirementsCount -1 do begin
        itemidamount.ItemID := item.GetRequirement(j).Item.ID;
        itemidamount.Activity := item.GetRequirement(j).Activity;
        itemidamount.Amount := item.GetRequirement(j).Amount;
        itemidamount.ApplyWaste := item.GetRequirement(j).ApplyWaste;
        itemidamount.Damage := item.GetRequirement(j).Damage;
        stream.Write(itemidamount, SizeOf(SItemIDAmount));
      end;
      itemtowrite2 := item.ToSNewItemUserSetting;
      stream2.Write(itemtowrite2, SizeOf(SNewItemUserSetting));
    end;
  end;
  if (adminhax) then begin
    try
      stream.SaveToFile('res\NewItemsNew.dat');
    except
      on e: Exception do MessageDlg(e.Message, mtError, [mbOk], 0);
    end;
  end;
  stream.Free;
  try
    stream2.SaveToFile('user\UserItems.dat');
  except
    on e: Exception do MessageDlg(e.Message, mtError, [mbOk], 0);
  end;
  stream2.Free;
end;


procedure TInventionCalc.SaveNewItemsText;
{
  Saves NewItems.dat and UserItems.dat, as latest version.
}
var
  i, j: Integer;
  itemidamount: SItemIDAmount;
  item: TNewItem;
  itemtowrite: SNewItem;
  version: Double;
  stream:TStringStream;
  mstream:TMemoryStream;
begin
  version := 0.41;
  stream := TStringStream.Create('');
  stream.WriteString(FloatToStr(version)+#13#10#13#10);

  for i := 0 to NewItems.Size -1 do begin
    item := TNewItem(NewItems.GetItem(i));
    if (item <> nil) then begin

      itemtowrite := item.ToSNewItem;
      stream.WriteString(
      itemtowrite.Name + #9 +
      IntToStr(itemtowrite.ID) + #9 +
      EItemTypeToStringShort(itemtowrite.ItemType) + #9 +
      FloatToStr(itemtowrite.Volume) + #9 +
      ERaceToStringShort(itemtowrite.Race) + #9 +
      IntToStr(itemtowrite.BuildTime) + #9 +
      IntToStr(itemtowrite.ProductivityModifier) + #9 +
      IntToStr(itemtowrite.InventionTime) + #9 +
      EInventionTypeToStringShort(itemtowrite.InventionType) + #9 +
      ERaceToStringShort(itemtowrite.InventionRace) + #9 +
      IntToStr(itemtowrite.CopyTime) + #9 +
      IntToStr(itemtowrite.ProductionLimit) + #9 +
      FloatToStr(itemtowrite.BaseChance) + #9 +
      IntToStr(itemtowrite.WasteFactor) + #9 +
      EInventionTypeToStringShort(itemtowrite.BuildType) + #9 +
      IntToStr(itemtowrite.PortionSize) + #9 +

      IntToStr(itemtowrite.UsedInAmount) + #9 +
      IntToStr(itemtowrite.RequirementAmount) + #9 +
      IntToStr(itemtowrite.InventedWithAmount) + #13#10 );

      for j := 0 to item.UsedInCount -1 do begin
        itemidamount.ItemID := item.GetUsedIn(j).Item.ID;
        itemidamount.Activity := ACT_None;
        itemidamount.Amount := item.GetUsedIn(j).Amount;
        itemidamount.ApplyWaste := item.GetUsedIn(j).ApplyWaste;
        itemidamount.Damage := item.GetUsedIn(j).Damage;

        stream.WriteString('<-'#9 +
          IntToStr(itemidamount.ItemID) + #9 +
          EActivityToString(itemidamount.Activity) + #9 +
          IntToStr(itemidamount.Amount) + #9 +
          BoolToStr(itemidamount.ApplyWaste) + #9 +
          FloatToStr(itemidamount.Damage) + #13#10);
      end;
      for j := 0 to item.RequirementsCount -1 do begin
        itemidamount.ItemID := item.GetRequirement(j).Item.ID;
        itemidamount.Activity := item.GetRequirement(j).Activity;
        itemidamount.Amount := item.GetRequirement(j).Amount;
        itemidamount.ApplyWaste := item.GetRequirement(j).ApplyWaste;
        itemidamount.Damage := item.GetRequirement(j).Damage;

        stream.WriteString('->'#9 +
          IntToStr(itemidamount.ItemID) + #9 +
          EActivityToString(itemidamount.Activity) + #9 +
          IntToStr(itemidamount.Amount) + #9 +
          BoolToStr(itemidamount.ApplyWaste) + #9 +
          FloatToStr(itemidamount.Damage) + #13#10);
      end;

    end;
  end;
  if (adminhax) then begin
    try
      stream.Position:=0;
      mstream:=TMemoryStream.Create;
      mstream.LoadFromStream(stream);
      mstream.SaveToFile('res\NewItemsNew.txt');
      mstream.Free;
    except
      on e: Exception do MessageDlg(e.Message, mtError, [mbOk], 0);
    end;
  end;
  stream.Free;
end;


procedure TInventionCalc.LinkShoppingListItems;
var
  i: Integer;
  shopitem: TShoppingItem;
begin
  for i := 0 to ShoppingList.Count -1 do begin
    shopitem := ShoppingList.GetItemByIndex(i);
    shopitem.Decryptor := self.GetDecryptorByID(shopitem.DecryptorID);
    shopitem.Item := self.GetItem(shopitem.ItemID);
  end;
  for i := 0 to ShoppingList.BuyCount -1 do begin
    shopitem := ShoppingList.GetBuyItemByIndex(i);
    shopitem.Decryptor := self.GetDecryptorByID(shopitem.DecryptorID);
    shopitem.Item := self.GetItem(shopitem.ItemID);
  end;
  for i := 0 to ShoppingList.ManufactureCount -1 do begin
    shopitem := ShoppingList.GetManufactureItemByIndex(i);
    shopitem.Decryptor := self.GetDecryptorByID(shopitem.DecryptorID);
    shopitem.Item := self.GetItem(shopitem.ItemID);
  end;
end;

procedure TInventionCalc.LoadResults;
var
  stream: TMemoryStream;
  version: Double;
begin
  if (not FileExists('user\InventionResults.dat')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\user\InventionResults.dat.', mtError, [mbOk], 0);
  end
  else begin
    stream := TMemoryStream.Create;
    stream.Position := 0;
    stream.LoadFromFile('user\InventionResults.dat');
    try
      stream.Read(version, sizeof(Double));
    except
    on e:Exception do
    begin
      MessageDlg('Error reading InventionResults.dat version information: '+e.Message, mtError, [mbOk], 0);
      stream.Free;
      exit;
    end;
    end;
    if (Round(version * 100) = 41) then // just 0.41 seems to result in a precision error
      self.LoadResults_v041(stream)
    else begin
      MessageDlg('Error reading InventionResults.dat: no suitable load function found.', mtError, [mbOk], 0);
      stream.Free;
      exit;
    end;
    stream.Free;
  end;
end;

function TInventionCalc.LoadResults_v041(stream: TMemoryStream): Boolean;
var
  i: Integer;
  configcount: Integer;
  config: SConfiguration;
  res: SResult;
begin
  result := true;
  stream.Read(configcount, SizeOf(Integer));
  for i := 0 to configcount -1 do begin
    try
      stream.Read(config, SizeOf(SConfiguration));
      Results.AddConfiguration(config.Name, GetDecryptorTypeByID(config.DecryptorTypeID), config.Metalevel, config.BPCMaxRuns, config.BPCRuns, config.BPCME, config.BPCPE, Locations.GetLocationByID(config.LocationID));
    except
      on e: Exception do begin
        MessageDlg('Error reading InventionResults.dat: invalid configuration. '+ e.Message, mtError, [mbOk], 0);
        result := false;
        exit;
      end;
    end;
  end;
  while stream.Position < stream.Size do begin
    try
      stream.Read(res, SizeOf(SResult));
      Results.AddResult(GetItem(res.ItemID), Results.GetConfiguration(res.ConfigurationID), res.Success, res.Date);
    finally

    end;
  end;  
end;

procedure TInventionCalc.ClearItems;
var
  i: Integer;
begin
  for i := 0 to self.GetNewItemCount -1 do begin
    if (GetItemByIndex(i) <> nil) then GetItemByIndex(i).Destroy;
  end;
  NewItems.Destroy;
  NewItems := THashTable.Create(2000, true);
end;

end.
